package com.aoyang.modbus.utils;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import com.aoyang.modbus.constant.BusModel;
import com.aoyang.modbus.constant.ValueType;
import com.aoyang.modbus.wrapper.SerialPortWrapperImpl;
import com.serotonin.modbus4j.ModbusFactory;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.code.DataType;
import com.serotonin.modbus4j.exception.ErrorResponseException;
import com.serotonin.modbus4j.exception.ModbusInitException;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.serotonin.modbus4j.ip.IpParameters;
import com.serotonin.modbus4j.locator.BaseLocator;
import com.serotonin.modbus4j.msg.WriteCoilRequest;
import com.serotonin.modbus4j.msg.WriteCoilResponse;
import jssc.SerialPort;

public class ModBusUtils {

    public static ModbusFactory modbusFactory = null;

    static {
        if (ObjectUtil.isEmpty(modbusFactory)) {
            modbusFactory = new ModbusFactory();
        }
    }

    public static ModbusMaster getMaster(String host, int port) {
        // UDP 协议
        ModbusMaster master;
        try {
            IpParameters params = new IpParameters();
            params.setHost(host);
            params.setPort(port);
            master = modbusFactory.createUdpMaster(params);
            master.init();
            return master;
        } catch (ModbusInitException e) {
            e.printStackTrace();
            throw new RuntimeException("创建 Modbus 链接失败", e);
        }
    }

    public static ModbusMaster getMaster(String host, int port, boolean isKeepAlive) {
        // TCP 协议
        ModbusMaster master;
        try {
            IpParameters params = new IpParameters();
            params.setHost(host);
            params.setPort(port);
            master = modbusFactory.createTcpMaster(params, isKeepAlive);
            master.init();
            return master;
        } catch (ModbusInitException e) {
            e.printStackTrace();
            throw new RuntimeException("创建 Modbus 链接失败", e);
        }
    }

    public static ModbusMaster getMaster(String commPortId, BusModel busModel) {
        ModbusMaster master = null;
        try {
            // RTU 协议
            if (BusModel.RTU.equals(busModel)) {
                SerialPortWrapperImpl wrapper = new SerialPortWrapperImpl(commPortId, 9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE, SerialPort.PARITY_NONE, SerialPort.PARITY_NONE);
                master = modbusFactory.createRtuMaster(wrapper);
            }

            // ASCII 协议
            if (BusModel.ASCII.equals(busModel)) {
                SerialPortWrapperImpl wrapper = new SerialPortWrapperImpl(commPortId, 9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE, SerialPort.PARITY_NONE, SerialPort.PARITY_NONE);
                master = modbusFactory.createAsciiMaster(wrapper);
            }

            if (master == null) {
                throw new ModbusInitException("创建 Modbus 链接失败");
            }

            master.init();
            return master;
        } catch (ModbusInitException e) {
            e.printStackTrace();
            throw new RuntimeException("创建 Modbus 链接失败", e);
        }
    }

    public static String readValue(ModbusMaster modbusMaster, int functionCode, int slaveId, int offset, int dataType) throws ModbusTransportException, ErrorResponseException {
        switch (functionCode) {
            case 1:
                // 0区	输出线圈	可读可写布尔量	00001-09999
                BaseLocator<Boolean> coilLocator = BaseLocator.coilStatus(slaveId, offset);
                Boolean coilValue = modbusMaster.getValue(coilLocator);
                return String.valueOf(coilValue);
            case 2:
                // 1区	输入线圈	只读布尔量	10001-19999
                BaseLocator<Boolean> inputLocator = BaseLocator.inputStatus(slaveId, offset);
                Boolean inputStatusValue = modbusMaster.getValue(inputLocator);
                return String.valueOf(inputStatusValue);
            case 3:
                // 3区	输入寄存器	只读寄存器	30001-39999
                BaseLocator<Number> holdingLocator = BaseLocator.holdingRegister(slaveId, offset, dataType);
                Number holdingValue = modbusMaster.getValue(holdingLocator);
                return String.valueOf(holdingValue);
            case 4:
                // 4区	保持寄存器	可读可写寄存器	40001-49999
                BaseLocator<Number> inputRegister = BaseLocator.inputRegister(slaveId, offset, dataType);
                Number inputRegisterValue = modbusMaster.getValue(inputRegister);
                return String.valueOf(inputRegisterValue);
            default:
                return "0";
        }
    }

    public static boolean writeValue(ModbusMaster master, int functionCode, int slaveId, int offset, String type, String value) throws ModbusTransportException, ErrorResponseException {
        switch (functionCode) {
            case 1:
                // 1区	输入线圈	只读布尔量	10001-19999
                boolean coilValue = toValue(type, value);
                WriteCoilRequest coilRequest = new WriteCoilRequest(slaveId, offset, coilValue);
                WriteCoilResponse coilResponse = (WriteCoilResponse) master.send(coilRequest);
                return !coilResponse.isException();
            case 3:
                // 3区	输入寄存器	只读寄存器	30001-39999
                BaseLocator<Number> locator = BaseLocator.holdingRegister(slaveId, offset, getValueType(type));
                master.setValue(locator, toValue(type, value));
                return true;
            default:
                return false;
        }
    }

    private static <T> T toValue(String type, String value) {
        return Convert.convertByClassName(getTypeClassName(type), value);
    }

    private static String getTypeClassName(String type) {
        String className = String.class.getName();
        switch (type.toLowerCase()) {
            case ValueType.BYTE:
                className = Byte.class.getName();
                break;
            case ValueType.SHORT:
                className = Short.class.getName();
                break;
            case ValueType.INT:
                className = Integer.class.getName();
                break;
            case ValueType.LONG:
                className = Long.class.getName();
                break;
            case ValueType.FLOAT:
                className = Float.class.getName();
                break;
            case ValueType.DOUBLE:
                className = Double.class.getName();
                break;
            case ValueType.BOOLEAN:
                className = Boolean.class.getName();
                break;
            default:
                break;
        }
        return className;
    }

    private static int getValueType(String type) {
        switch (type.toLowerCase()) {
            case ValueType.LONG:
                return DataType.FOUR_BYTE_INT_SIGNED;
            case ValueType.FLOAT:
                return DataType.FOUR_BYTE_FLOAT;
            case ValueType.DOUBLE:
                return DataType.EIGHT_BYTE_FLOAT;
            default:
                return DataType.TWO_BYTE_INT_SIGNED;
        }
    }

}
